/*
 * Copyright (C) 1995.  Bill Brown <brown@gis.uiuc.edu> & Michael Shapiro
 *
 * This program is free software under the GPL (>=v2)
 * Read the file GPL.TXT coming with GRASS for details.
 */
#include <stdlib.h>
#include <grass/datetime.h>
#include "math.h"

/*************************************************************/
/*
	This performs the formula:   result = a - b;

	both a and b must be absolute.
	result will be relative
	If a is "earlier" than b, then result should be set negative.

	b must be no more "precise" than a.
	   (a copy of b is "extended" to the precision of a)

	       datetime_copy (tb, b)
	       datetime_reset_from_to (tb, b.from, a.to, a.fracsec))


	If result.to == SECOND, then result.fracsec is a.fracsec

	result will have the following from/to based on a.to:

			     result
		a.to      from    to
		YEAR      YEAR    YEAR
		MONTH     YEAR    MONTH
		DAY       DAY     DAY
		HOUR      DAY     HOUR
		MINUTE    DAY     MINUTE
		SECOND    DAY     SECOND

	If either 'a' or 'b' has a timezone, both must have a timezone.
	The difference will account for the differences in the time zones.
*/

static int _datetime_ymd_to_ddays(const DateTime *, double *);
static int _datetime_compare(const DateTime *, const DateTime *);


/*!
 * \brief 
 *
 * 
 * This performs the formula:   result = a - b; 
 * <ul>
<li> both a and b must be absolute.
 * </li>
<li> result will be relative
 * </li>
<li> If a is "earlier" than b, then result will be set negative.
 * </li>
<li> b must be no more "precise" than a.
 * (a copy of b is "extended" to the precision of a)
 * </li>
<li> If result.to == SECOND, then result.fracsec is a.fracsec
 * </li>
<li> result will have the following from/to based
 * on a.to: result a.to      from    to YEAR      YEAR    YEAR MONTH     YEAR
 * MONTH DAY       DAY     DAY HOUR      DAY     HOUR MINUTE    DAY   
 * MINUTE SECOND    DAY     SECOND [LAYOUT ??? - see HTML]
 * </li>
<li> If either 'a' or 'b' has a timezone, both must have a timezone. The
 * difference will account for the differences in the time zones.
</li></ul>

 *
 *  \param a
 *  \param b
 *  \param result
 *  \return int
 */

int
datetime_difference (const DateTime *a, const DateTime *b, DateTime *result)
{
    DateTime tb, ta, *early, *late;
    int compare, tzmin;

    /* if not both absolute, return error */
    
    datetime_copy (&tb, b);
    datetime_change_from_to (&tb, DATETIME_YEAR, a->to, a->fracsec);

    datetime_copy (&ta, a);
    if(datetime_get_timezone (&ta, &tzmin) == 0 ||
		datetime_get_timezone (&tb, &tzmin) == 0){
	if(datetime_get_timezone (&ta, &tzmin) == 0 &&
		    datetime_get_timezone (&tb, &tzmin) == 0){
	    datetime_change_to_utc (&ta);
	    datetime_change_to_utc (&tb);
	}
	else
	    return datetime_error (-1, 
			       "only one opperand contains valid timezone");
    }
    
    /* initialize result */
    datetime_set_type(result, DATETIME_RELATIVE, 
		    ta.to < DATETIME_DAY? DATETIME_YEAR: DATETIME_DAY, 
		    ta.to, ta.fracsec);
    compare = _datetime_compare(&ta, &tb);
    if(compare > 0){
	early = &tb;
	late = &ta;
	result->positive = 1;
    }
    else if(compare < 0){
	early = &ta;
	late = &tb;
	result->positive = 0;
    }
    else{ /* equal */
	return(0);
    }

    /* now the work */
    if(datetime_in_interval_year_month (ta.to)){
	int dm;

	if(ta.positive == tb.positive){
	    /* change if we use doubles! */
	    result->year = abs(late->year - early->year);
	}
	else{
	    result->year = late->year + early->year - 2;
	}
	dm = late->month - early->month;
	if(dm >= 0)
	    result->month = dm;
	else{
	    result->year -= 1;
	    result->month = dm + 12;
	}
    }
    else{
	DateTime erel, lrel;
	double latedays, earlydays;

	datetime_set_increment_type(a, &erel);
	_datetime_ymd_to_ddays(early, &earlydays);
	/* copy day -> down */
	erel.day = earlydays;
	erel.hour = early->hour;
	erel.minute = early->minute;
	erel.second = early->second;

	datetime_set_increment_type(a, &lrel);
	_datetime_ymd_to_ddays(late, &latedays);
	/* copy day -> down */
	lrel.day = latedays;
	lrel.hour = late->hour;
	lrel.minute = late->minute;
	lrel.second = late->second;
	
	datetime_invert_sign(&erel);
	datetime_increment(&erel, &lrel);

	/* copy erel back to result */
	result->day = erel.day;
	result->hour = erel.hour;
	result->minute = erel.minute;
	result->second = erel.second;

	/* need carry? */
    }

    return(0);
}

/*************************************************************/
/* returns 1 if a is later than b, 
	  -1 if a is earlier than a,
	   0 otherwise 
*/ 
/* only looks at from-to fields defined by a */

static int _datetime_compare(const DateTime *a, const DateTime *b)
{
     int i;

    if(a->positive && !b->positive)
	return(1);
    else if(b->positive && !a->positive)
	return(-1);

    /* same signs */
    for(i = a->from; i <= a->to; i++){
	switch(i){

	    case DATETIME_SECOND:
		if(a->second > b->second)
		    return(1);
		else if(a->second < b->second)
		    return(-1);
		break;

	    case DATETIME_MINUTE:
		if(a->minute > b->minute)
		    return(1);
		else if(a->minute < b->minute)
		    return(-1);
		break;

	    case DATETIME_HOUR:
		if(a->hour > b->hour)
		    return(1);
		else if(a->hour < b->hour)
		    return(-1);
		break;

	    case DATETIME_DAY:
		if(a->day > b->day)
		    return(1);
		else if(a->day < b->day)
		    return(-1);
		break;

	    case DATETIME_MONTH:
		if(a->month > b->month)
		    return(1);
		else if(a->month < b->month)
		    return(-1);
		break;

	    case DATETIME_YEAR: /* only place sign matters */
		if(a->positive){
		    if(a->year > b->year)
			return(1);
		    else if(a->year < b->year)
			return(-1);
		}
		else{
		    if(a->year < b->year)
			return(1);
		    else if(a->year > b->year)
			return(-1);
		}
		break;
	}
    }
    return(0);


}

/*************************************************************/

static int _datetime_ymd_to_ddays(const DateTime *dtymd, double *days)          /* note extra precision! */
{
    int yr, mo;


    *days = 0.0;

    if(dtymd->positive){
	*days = dtymd->day - 1;                   /* start w/ days - 1*/
	for (mo = dtymd->month - 1; mo > 0; mo--){ /* add earlier months */
	    *days += datetime_days_in_month(dtymd->year, mo, dtymd->positive);
	}
	for (yr = dtymd->year - 1; yr > 0; yr--){ /* add earlier years */
	    *days += datetime_days_in_year(yr, dtymd->positive);
	}
    }
    else{
	for (yr = dtymd->year - 1; yr > 0; yr--){ /* add later years */
	    *days += datetime_days_in_year(yr, dtymd->positive);
	}
	for (mo = 12; mo >= dtymd->month; mo--){ /*add current & later months*/
	    *days += datetime_days_in_month(dtymd->year, mo, dtymd->positive);
	}
	*days -= dtymd->day;                   /* subtract current days */
    }

    return 0;
}

/*************************************************************/
/*************************************************************/
